package com.xb.cloud.common.keygen;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class DefaultKeyGenerator implements KeyGenerator {
	private final Logger LOGGER = LoggerFactory.getLogger(DefaultKeyGenerator.class);

	public static final long EPOCH;

	private static final long SEQUENCE_BITS = 12L;

	private static final long WORKER_ID_BITS = 10L;

	private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

	private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

	private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

	private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;

	private static long workerId;

	static {
		Calendar calendar = Calendar.getInstance();
		calendar.set(2016, Calendar.NOVEMBER, 1);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		EPOCH = calendar.getTimeInMillis();
	}

	private long sequence;

	private long lastTime;

	/**
	 * Set work process id.
	 * 
	 * @param workerId
	 *            work process id
	 */
	public static void setWorkerId(final long workerId) {
		if (workerId > WORKER_ID_MAX_VALUE || workerId < 0) {
			throw new IllegalArgumentException(
					String.format("worker Id can't be greater than %d or less than 0", WORKER_ID_MAX_VALUE));
		}
		DefaultKeyGenerator.workerId = workerId;
	}

	/**
	 * Generate key.
	 * 
	 * @return key type is @{@link Long}.
	 */
	@Override
	public synchronized Long generateKey() {
		long currentMillis = System.currentTimeMillis();

		if (currentMillis < lastTime) {
			throw new RuntimeException(String.format(
					"Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTime - currentMillis));
		}
		if (lastTime == currentMillis) {
			if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
				currentMillis = waitUntilNextTime(currentMillis);
			}
		} else {
			sequence = 0;
		}
		lastTime = currentMillis;
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)),
					workerId, sequence);
		}
		return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS)
				| sequence;
	}

	private long waitUntilNextTime(final long lastTime) {
		long time = System.currentTimeMillis();
		while (time <= lastTime) {
			time = System.currentTimeMillis();
		}
		return time;
	}
}